# gozerbot/xmpp/bot.py
#
#

""" jabber bot definition """

__copyright__ = 'this file is in the public domain'

## IMPORT SECTION

# gozerbot imports
from gozerbot.users import users
from gozerbot.utils.log import rlog
from gozerbot.utils.exception import handle_exception
from gozerbot.utils.trace import whichmodule
from gozerbot.utils.locking import lockdec
from gozerbot.utils.generic import waitforqueue, toenc, fromenc, jabberstrip, getrandomnick
from gozerbot.config import config
from gozerbot.persist.pdod import Pdod
from gozerbot.utils.dol import Dol
from gozerbot.datadir import datadir
from gozerbot.channels import Channels
from gozerbot.less import Less
from gozerbot.ignore import shouldignore
from gozerbot.callbacks import jcallbacks
from gozerbot.threads.thr import start_new_thread
from gozerbot.fleet import fleet
from gozerbot.botbase import BotBase

# xmpp imports
from presence import Presence
from message import Message
from iq import Iq
from core import XMLStream
from monitor import xmppmonitor
from wait import XMPPWait, XMPPErrorWait
from jid import JID, InvalidJID

# basic imports
import time, Queue, os, threading, thread, types, xml, re, hashlib

## END IMPORT

## LOCK SECTION

# locks
outlock = thread.allocate_lock()
inlock = thread.allocate_lock()
connectlock = thread.allocate_lock()
outlocked = lockdec(outlock)
inlocked = lockdec(inlock)
connectlocked = lockdec(connectlock)

## END LOCK

class Bot(XMLStream, BotBase):

    """
        xmpp bot class.

        :param name: name of the xmpp bot
        :type name: string
        :param cfg: config file for the bot
        :type config: gozerbot.config.Config
        :rtype: None

    """

    def __init__(self, name, cfg={}):
        BotBase.__init__(self, name, cfg)

        if not self.port:
            self.port = 5222

        if not self.host:
            raise Exception("host not set in %s xmpp bot" % self.name)

        self.username = self.user.split('@')[0]
        XMLStream.__init__(self, self.host, self.port, self.name)   
        self.type = 'xmpp'
        self.outqueue = Queue.Queue()
        self.sock = None
        self.me = self.user
        self.jid = self.me
        self.lastin = None
        self.test = 0
        self.connecttime = 0
        self.connection = None
        self.privwait = XMPPWait()
        self.errorwait = XMPPErrorWait()
        self.monitor = xmppmonitor
        self.jabber = True
        self.connectok = threading.Event()
        self.jids = {}
        self.topics = {}
        self.timejoined = {}
        self.channels409 = []

        if not self.state.has_key('ratelimit'):
            self.state['ratelimit'] = 0.05

        if self.port == 0:
            self.port = 5222

        self.monitor.start()
    
    def _resumedata(self):

        """
             return data needed for resuming.

             .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                 :pyobject: Bot._resumedata

        """

        return {self.name: [self.server, self.user, self.password, self.port]}

    def _outputloop(self):

        """
            loop to take care of output to the server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot._outputloop

        """

        rlog(10, self.name, 'starting outputloop')
        lastsend = time.time()
        charssend = 0

        while not self.stopped:
            time.sleep(0.01)
            what = self.outqueue.get()

            if self.stopped or what == None:
                 break

            if charssend + len(what) < 1000:

                try:
                    self._raw(what)
                except Exception, ex:
                    self.error = str(ex)
                    handle_exception
                    continue

                lastsend = time.time()
                charssend += len(what)

            else:

                if time.time() - lastsend > 1:

                    try:
                        self._raw(what)
                    except Exception, ex:
                        handle_exception()
                        continue

                    lastsend = time.time()
                    charssend = len(what) 
                    continue

                charssend = 0
                sleeptime = self.cfg['jabberoutsleep'] or config['jabberoutsleep']

                if not sleeptime:
                    sleeptime = 1

                rlog(10, self.name, 'jabberoutsleep .. sleeping %s seconds' % sleeptime)
                time.sleep(sleeptime)

                try:
                    self._raw(what)
                except Exception, ex:
                    handle_exception()

        rlog(10, self.name, 'stopping outputloop .. %s' % (self.error or 'no error set'))

    def _keepalive(self):

        """ 
             keepalive method .. send empty string to self every 3 minutes.

             .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                 :pyobject: Bot._keepalive

        """

        nrsec = 0
        


        while not self.stopped:
            time.sleep(1)
            nrsec += 1

            if nrsec < 180:
                continue
            else:
                nrsec = 0

            self.sendpresence()

    def sendpresence(self):
        """
            send presence based on status and status text
            set by user

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                 :pyobject: Bot.sendpresence

        """

        if self.state.has_key('status') and self.state['status']:
            status = self.state['status']
        else:
            status = ""
        if self.state.has_key('show') and self.state['show']:
            show = self.state['show']
        else:
            show = ""
        rlog(4, self.name, 'keepalive: %s - %s' % (show, status))
        if show and status:
            p = Presence({'to': self.me, 'show': show, 'status': status}, self)
        elif show:
            p = Presence({'to': self.me, 'show': show }, self)
        elif status:
            p = Presence({'to': self.me, 'status': status}, self)
        else:
            p = Presence({'to': self.me }, self)

        self.send(p)

    def _keepchannelsalive(self):

        """
            channels keep alive method.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot._keepchannelsalive

        """

        nrsec = 0
        p = Presence({'to': self.me, 'txt': '' }, self)

        while not self.stopped:
            time.sleep(1)
            nrsec += 1

            if nrsec < 600:
                continue
            else:
                nrsec = 0

            for chan in self.state['joinedchannels']:

                if chan not in self.channels409:
                    p = Presence({'to': chan}, self)
                    self.send(p)

    def connect(self, reconnect=True):

        """
            connect the xmpp server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.connect

        """


        if self.stopped:
            rlog(100, self.name, 'bot is stopped .. not connecting')
            return
        try:
            if not XMLStream.connect(self):
                rlog(10, self.name, 'connect to %s:%s failed' % (self.host, self.port))
                return
            else:
                rlog(10, self.name, 'connected')
            self.logon(self.user, self.password)
            start_new_thread(self._outputloop, ())
            start_new_thread(self._keepalive, ())
            #start_new_thread(self._keepchannelsalive, ())
            self.requestroster()
            self._raw("<presence/>")
            self.connectok.set()
            self.sock.settimeout(None)
            return True
        except Exception, ex:
            handle_exception()
            if reconnect:
                self.reconnect()

    def logon(self, user, password):

        """
            logon the xmpp server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.logon

        """

        iq = self.initstream()
        if not self.auth(user, password, iq.id):
            if self.register(user, password):
                time.sleep(5)
                self.auth(user, password)
            else:
                self.exit()
                return
        XMLStream.logon(self)
 
    def initstream(self):

        """
             send initial string sequence to the xmpp server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.initstream

        """


        rlog(10, self.name, 'starting initial stream sequence')
        self._raw("""<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>""" % (self.user.split('@')[1], )) 
        result = self.connection.read()
        iq = self.loop_one(result)
        rlog(3, self.name, "initstream: %s" % result)
        return iq

    def register(self, jid, password):

        """
            register the jid to the server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.register

        """

        try:
            resource = jid.split("/")[1]
        except IndexError:
            resource = "gozerbot"
        rlog(10, self.name, 'registering %s' % jid)
        self._raw("""<iq type='get'><query xmlns='jabber:iq:register'/><username>%s</username></iq>""" % jid.split("@")[0])
        #self._raw("""<iq type='get' id='reg1' />""")
        result = self.connection.read()
        #rlog(3, self.name, 'register: %s' % result)
        iq = self.loop_one(result)
        if not iq:
            rlog(10, self.name, "unable to register")
            return
        rlog(3, self.name, 'register: %s' % str(iq))
        self._raw("""<iq type='set'><query xmlns='jabber:iq:register'><username>%s</username><resource>%s</resource><password>%s</password></query></iq>""" % (jid.split('@')[0], resource, password))
        result = self.connection.read()
        rlog(3, self.name, 'register: %s' % result)
        if not result:
            return False
        iq = self.loop_one(result)
        if not iq:
            rlog(10, self.name, "can't decode data: %s" % result)
            return False
        rlog(3, self.name, 'register: %s' % result)
        if iq.error:
            rlog(10, self.name, 'REGISTER FAILED: %s' % iq.error)
            if iq.error.code == "405":
                rlog(10, self.name, "this server doesn't allow registration by the bot, you need to create an account for it yourself")
            else:
                rlog(10, self.name, result)
            self.error = iq.error
            return False
        rlog(10, self.name, 'register ok')
        return True

    def auth(self, jid, password, digest=""):

        """
            auth against the xmpp server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.auth

        """

        rlog(10, self.name, 'authing %s' % jid)
        name = jid.split('@')[0]
        rsrc = self.cfg['resource'] or config['resource'] or 'gozerbot';
        self._raw("""<iq type='get'><query xmlns='jabber:iq:auth'><username>%s</username></query></iq>""" % name)
        result = self.connection.read()
        iq = self.loop_one(result)
        rlog(3, self.name, 'auth:' + result)
        if ('digest' in result) and digest:
            s = hashlib.new('SHA1')
            s.update(digest)
            s.update(password)
            d = s.hexdigest()
            self._raw("""<iq type='set'><query xmlns='jabber:iq:auth'><username>%s</username><digest>%s</digest><resource>%s</resource></query></iq>""" % (name, d, rsrc))
        else:
            self._raw("""<iq type='set'><query xmlns='jabber:iq:auth'><username>%s</username><resource>%s</resource><password>%s</password></query></iq>""" % (name, rsrc, password))
        result = self.connection.read()
        iq = self.loop_one(result)
        if not iq:
            rlog(10, self.name, 'auth failed: %s' % result)
            return False        
        rlog(3, self.name, 'auth: ' + result)
        if iq.error:
            rlog(10, self.name, 'AUTH FAILED: %s' % iq.error)
            if iq.error.code == "401":
                rlog(10, self.name, "wrong user or password (or user not known)")
            else:
                rlog(10, self.name, result)
            self.error = iq.error
            return False
        rlog(10, self.name, 'auth ok')
        return True

    def requestroster(self):

        """
            request roster from xmpp server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.requestroster

        """

        self._raw("<iq type='get'><query xmlns='jabber:iq:roster'/></iq>")

    def disconnectHandler(self, ex):

        """
            disconnect handler.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.disconnectHandler

        """

        rlog(100, self.name, "disconnected: %s" % str(ex)) 

        if not self.stopped:
            self.reconnect()
        else:
            self.exit()

    def joinchannels(self):

        """
            join all already joined channels.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.joinchannels

        """


        for i in self.state['joinedchannels']:
            key = self.channels.getkey(i)
            nick = self.channels.getnick(i)
            result = self.join(i, key, nick)
            if result == 1:
                rlog(10, self.name, 'joined %s' % i)
            else:
                rlog(10, self.name, 'failed to join %s: %s' % (i, result))

    def broadcast(self, txt):

        """
            broadcast txt to all joined channels.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.broadcast

        """

        for i in self.state['joinedchannels']:
            self.say(i, txt)

    def handle_iq(self, data):

        """
            iq handler .. overload this when needed.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.handle_iq

        """

        pass

    def handle_message(self, data):

        """
            message handler.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.handle_message

        """

        if self.test:
            return
 
        m = Message(data, self)

        if data.type == 'groupchat' and data.subject:
            self.topiccheck(m)
            nm = Message(m, bot=self)
            jcallbacks.check(self, nm)
            return

        if data.get('x').xmlns == 'jabber:x:delay':
            rlog(5, self.name, "ignoring delayed message")
            return

        self.privwait.check(m)

        if m.isresponse:
            return

        if m.groupchat:
            m.msg = False

        jid = None
        m.origjid = m.jid

        for node in m.subelements:
            try:
                m.jid = node.x.item.jid 
            except (AttributeError, TypeError):
                continue

        #if not m.txt:
        #    return
        if self.me in m.fromm:
            return 0

        #if m.groupchat and self.nick == m.resource:
        #    return 0
        go = 0

        try:
            cc = self.channels[m.channel]['cc']
        except (TypeError, KeyError):
            cc = config['defaultcc'] or '!'

        try:
            channick = self.channels[m.channel]['nick']
        except (TypeError, KeyError):
            channick = self.nick

        if m.msg and not config['noccinmsg']:
            go = 1
        if not m.txt:
            go = 0
        elif m.txt[0] in cc:
            go = 1
        elif m.txt.startswith("%s: " % channick):
            m.txt = m.txt.replace("%s: " % channick, "")
            go = 1
        elif m.txt.startswith("%s, " % channick):
            m.txt = m.txt.replace("%s, " % channick, "")
            go = 1

        if m.txt and m.txt[0] in cc:
            m.txt = m.txt[1:]

        if go:
            try:
                from gozerbot.plugins import plugins
                if plugins.woulddispatch(self, m):
                    m.usercmnd = True
                plugins.trydispatch(self, m)
            except:
                handle_exception()

        try:
            if m.type == 'error':
                if m.code:
                    rlog(10, self.name + '.error', str(m))
                self.errorwait.check(m)
                self.errorHandler(m)
        except Exception, ex:
            handle_exception()

        mm = Message(m, self)
        jcallbacks.check(self, mm)

    def errorHandler(self, event):

        """
            error handler .. calls the errorhandler set in the event.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.errorHandler

        """

        try:
            event.errorHandler()
        except AttributeError:
            rlog(10, self.name, 'unhandled error: %s' % event)

    def handle_presence(self, data):

        """
            presence handler.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.handle_presence

        """

        p = Presence(data, self)
        frm = p.fromm
        nickk = ""
        nick = p.nick

        if self.me in p.userhost:
            return 0

        if nick:
            self.userhosts.data[nick] = str(frm)
            nickk = nick

        jid = None

        for node in p.subelements:
            try:
                jid = node.x.item.jid 
            except (AttributeError, TypeError):
                continue

        if nickk and jid:
            channel = p.channel
            if not self.jids.has_key(channel):
                self.jids[channel] = {}
            self.jids[channel][nickk] = jid
            self.userhosts.data[nickk] = str(jid)
            rlog(0, self.name, 'setting jid of %s (%s) to %s' % (nickk, \
 channel, jid))

        if p.type == 'subscribe':
            pres = Presence({'to': p.fromm, 'type': 'subscribed'}, self)
            self.send(pres)
            pres = Presence({'to': p.fromm, 'type': 'subscribe'}, self)
            self.send(pres)

        nick = p.resource

        if p.type != 'unavailable':
            self.userchannels.adduniq(nick, p.channel)
            p.joined = True
            p.type = 'available'
        elif self.me in p.userhost:
            try:
                del self.jids[p.channel]
                rlog(0, self.name, 'removed %s channel jids' % p.channel)
            except KeyError:
                pass
        else:
            try:
                del self.jids[p.channel][p.nick]
                rlog(0, self.name, 'removed %s jid' % p.nick)
            except KeyError:
                pass

        pp = Presence(p, self)
        jcallbacks.check(self, pp)

        if p.type == 'error':
            for node in p.subelements:
                try:
                    err = node.error.code
                except (AttributeError, TypeError):
                    err = 'no error set'
                try:
                    txt = node.text.data
                except (AttributeError, TypeError):
                    txt = ""
            if err:
                rlog(10, self.name + '.error', "%s: %s"  % (err, txt))

            self.errorwait.check(p)

            try:
                method = getattr(self,'handle_' + err)
                # try to call method
                try:
                    method(p)
                except:
                    handle_exception()
            except AttributeError:
                # no command method to handle event
                pass

    def reconnect(self):

        """
            reconnect to the server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.reconnect

        """

        if self.stopped:
            rlog(100, self.name, 'bot is stopped .. not reconnecting')
            return

        rlog(100, self.name, 'reconnecting .. sleeping 15 seconds')
        self.exit()
        time.sleep(15)
        newbot = Bot(self.name, self.cfg)

        if newbot.connect():
            self.name += '.old'
            newbot.joinchannels()
            fleet.replace(self.name, newbot)
            return True

        return False


    def send(self, what):

        """
            send stanza to the server.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.send

        """

        try:
            to = what['to']
        except (KeyError, TypeError):
            rlog(10, self.name, "can't determine where to send %s to" % what)
            return

        try:
            jid = JID(to)
        except (InvalidJID, AttributeError):
            rlog(10, self.name, "invalid jid %s .. %s" % (str(to), str(what)))
            return

        try:
            del what['from']
            #del what['fromm']
        except KeyError:
            pass

        try:
            xml = what.toxml()
            if not xml:
                raise Exception("can't convert %s to xml .. bot.send()" % what) 
        except (AttributeError, TypeError):
            handle_exception()
            return
            #raise Exception("can't convert %s to xml .. bot.send()" % what) 
           
        self.outqueue.put(toenc(xml))
        self.monitor.put(self, what)

    def sendnocb(self, what):

        """
            send to server without calling callbacks/monitors.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.sendnocb

        """

        try:
            xml = what.toxml()
        except AttributeError:
            xml = what
        self.outqueue.put(toenc(xml))

    def action(self, printto, txt, fromm=None, groupchat=True):

        """
            send an action.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.action

        """

        txt = "/me " + txt

        if self.google:
            fromm = self.me

        if printto in self.state['joinedchannels'] and groupchat:
            message = Message({'to': printto, 'txt': txt, 'type': 'groupchat'}, self)
        else:
            message = Message({'to': printto, 'txt': txt}, self)

        if fromm:
            message.fromm = fromm

        self.send(message)
        
    def say(self, printto, txt, fromm=None, groupchat=True, speed=5, type="normal", how=''):

        """
            say txt to channel/JID.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.say

        """

        txt = jabberstrip(txt)

        if self.google:
            fromm = self.me

        if printto in self.state['joinedchannels'] and groupchat:
            message = Message({'to': printto, 'body': txt, 'type': 'groupchat'}, self)
        else:
            message = Message({'to': printto, 'body': txt, 'type': 'chat'}, self)

        if fromm:
            message.fromm = fromm
        else:
            message.fromm = self.me

        self.send(message)

    def saynocb(self, printto, txt, fromm=None, groupchat=True, speed=5, type="normal", how=''):

        """
             say txt to channel/JID without calling callbacks/monitors.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.saynocb

        """

        txt = jabberstrip(txt)

        #if self.google:
        #    fromm = self.me

        if printto in self.state['joinedchannels'] and groupchat:
            message = Message({'to': printto, 'body': txt, 'type': 'groupchat'}, self)
        else:
            message = Message({'to': printto, 'body': txt}, self)

        if fromm:
            message.fromm = fromm
        else:
            message.fromm = self.me

        self.send(message)

    def userwait(self, msg, txt):

        """
            wait for user response.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.userwait

        """

        msg.reply(txt)
        queue = Queue.Queue()
        self.privwait.register(msg, queue)
        result = queue.get()

        if result:
            return result.txt

    def save(self):

        """
            save bot's state.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.save

        """

        self.state.save()

    def quit(self):

        """
            send unavailable presence.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.quit

        """

        presence = Presence({'type': 'unavailable'}, self)

        for i in self.state['joinedchannels']:
            presence.to = i
            self.send(presence)

        presence = Presence({'type': 'unavailable'}, self)
        presence['from'] = self.me
        self.send(presence)
        time.sleep(1)
        
    def exit(self):

        """
            exit the bot.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.exit

        """

        self.quit()
        self.stopped = 1
        self.outqueue.put_nowait(None)
        self.save()
        time.sleep(3)
        rlog(10, self.name, 'exit')

    def join(self, channel, password=None, nick=None):

        """
            join conference.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.join

        """

        if '#' in channel:
            return

        try:
            if not nick:
                nick = channel.split('/')[1]
        except IndexError:
            nick = self.nick

        channel = channel.split('/')[0]

        if not self.channels.has_key(channel):
            # init channel data
            self.channels.setdefault(channel, {})

        # setup error wait
        q = Queue.Queue()
        self.errorwait.register("409", q, 3)
        self.errorwait.register("401", q, 3)
        self.errorwait.register("400", q, 3)

        # do the actual join
        presence = Presence({'to': channel + '/' + nick}, self)

        if password:
             presence.x.password = password             
#            passnode = Node('password')
#            passnode.addData(password)
#            presence.addChild(name='x', namespace='http://jabber.org/protocol/muc', \
#payload=[passnode, ])

        self.send(presence)
        errorobj = waitforqueue(q, 3)

        if errorobj:
            err = errorobj[0].error
            rlog(100, self.name, 'error joining %s: %s' % (channel, err))
            if err >= '400':
                if channel not in self.channels409:
                    self.channels409.append(channel)
            return err

        self.timejoined[channel] = time.time()
        chan = self.channels[channel]
        # if password is provided set it
        chan['nick'] = nick

        if password:
            chan['key'] = password

        # check for control char .. if its not there init to !
        if not chan.has_key('cc'):
            chan['cc'] = config['defaultcc'] or '!'

        if not chan.has_key('perms'):
            chan['perms'] = []

        self.channels.save()

        if channel not in self.state['joinedchannels']:
            self.state['joinedchannels'].append(channel)

        if channel in self.channels409:
            self.channels409.remove(channel)

        self.state.save()
        return 1

    def part(self, channel):
 
        """
            leave conference.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.part

        """

        if '#' in channel:
            return

        presence = Presence({'to': channel}, self)
        presence.type = 'unavailable'
        self.send(presence)

        if channel in self.state['joinedchannels']:
            self.state['joinedchannels'].remove(channel)

        self.state.save()
        return 1

    def outputnolog(self, printto, what, how, who=None, fromm=None):

        """
            do output but don't log it.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.outputnolog

        """

        if fromm and shouldignore(fromm):
            return

        self.saynocb(printto, what)

    def topiccheck(self, msg):

        """
            check if topic is set.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.topiccheck

        """

        if msg.groupchat:
            try:
                topic = msg.subject
                if not topic:
                    return None
                self.topics[msg.channel] = (topic, msg.userhost, time.time())
                rlog(0, self.name, 'topic of %s set to %s' % \
(msg.channel, topic))
            except AttributeError:
                return None

    def settopic(self, channel, txt):

        """
            set topic.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.settopic

        """

        pres = Message({'to': channel, 'subject': txt}, self)
        pres.type = 'groupchat'
        self.send(pres)

    def gettopic(self, channel):

        """
            get topic.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.gettopic

        """

        try:
            topic = self.topics[channel]
            return topic
        except KeyError:
            return None

    def domsg(self, msg):

        """
            dispatch an msg on the bot.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.domsg

        """

        from gozerbot.plugins import plugins
        plugins.trydispatch(self, msg)

    def sendraw(self, data):

        """
             compat function.

            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
                :pyobject: Bot.sendraw

        """

        self._raw(data)
